///////////////////////////////////////////////////////////////////////////////
//	FILENAME:	BBC.m 
//	SUMMARY:	Implementation of a Billiard-Ball Computation Annotation
//	SUPERCLASS:	Object
//	INTERFACE:	None
//	PROTOCOLS:	<DocNotification,Annotation,Tool,ASCIISupport>
//	AUTHOR:		Rohit Khare
//	COPYRIGHT:	(c) 1994 California Institure of Technology, eText Project
//	COPYRIGHT:	(c) 1992 Rohit Khare (core simulation code)
///////////////////////////////////////////////////////////////////////////////
//	DESCRIPTION
//		Separated out model from old BilliardView code.
///////////////////////////////////////////////////////////////////////////////
//	HISTORY
//	05/29/94:	Created as an eText Annotation.
//	1992-93:	Developed for CS20ab.
///////////////////////////////////////////////////////////////////////////////

#import "BBC.h"
/* to allow access without out-of-bound errors and allow wrap-around */
#define state(i,j) (state[(i+vres) %vres][(j+hres)%hres])
#define oldstate(i,j) (oldstate[(i+vres) %vres][(j+hres)%hres])

@implementation BBC
+ toolAwake:theApp
{
	char        buf[MAXPATHLEN];
	NXBundle	*bundle;
	NXImage		*theIcon;
	
	bundle = [NXBundle bundleForClass:[BBC class]];
	if ([bundle getPath:buf forResource:"N" ofType:"tiff"] ) {
		[[[NXImage alloc] initFromFile:buf] setName:"N"];
	} else {
		NXLogError("Image not found: N");
	}
	if ([bundle getPath:buf forResource:"S" ofType:"tiff"] ) {
		[[[NXImage alloc] initFromFile:buf] setName:"S"];
	} else {
		NXLogError("Image not found: S");
	}
	if ([bundle getPath:buf forResource:"E" ofType:"tiff"] ) {
		[[[NXImage alloc] initFromFile:buf] setName:"E"];
	} else {
		NXLogError("Image not found: E");
	}
	if ([bundle getPath:buf forResource:"W" ofType:"tiff"] ) {
		[[[NXImage alloc] initFromFile:buf] setName:"W"];
	} else {
		NXLogError("Image not found: W");
	}
	if ([bundle getPath:buf forResource:"Trail" ofType:"tiff"] ) {
		[[[NXImage alloc] initFromFile:buf] setName:"Trail"];
	} else {
		NXLogError("Image not found: Trail");
	}
	if ([bundle getPath:buf forResource:"Wall" ofType:"tiff"] ) {
		[[[NXImage alloc] initFromFile:buf] setName:"Wall"];
	} else {
		NXLogError("Image not found: Wall");
	}
	if ([bundle getPath:buf forResource:"Grid" ofType:"tiff"] ) {
		[[[NXImage alloc] initFromFile:buf] setName:"Grid"];
	} else {
		NXLogError("Image not found: Grid");
	}
	if ( [bundle getPath:buf forResource:"BBC" ofType:"tiff"] ) {
		theIcon = [[NXImage alloc] initFromFile:buf];
		[theIcon setName:"BBC"];
	} else {
		NXLogError("Image not found: BBC");
		theIcon = nil;
	}
	[theApp   registerAnnotation: [BBC class] 
							name: "BBC"
					RTFDirective: "BBC"
					   menuLabel: "Simulations/Insert BBC..."
						 menuKey: 0
						menuIcon: (NXImage *) theIcon];
	[theApp registerType:NXCreateFileContentsPboardType("bbc")
				for:[BBC class]];
	return self;
}

- init 
{	char buf[MAXPATHLEN];
	NXRect rt;
	
	[super init];
	state = oldstate = NULL;
	sprintf(buf, "Untitled BBC %d", [NXApp uniqueID]);
	theName = NXUniqueString(buf);
	isAccelerated = NO;
	isEditable = doesShowPath = YES;
	bbcView = [[BilliardView alloc] init];
	[bbcView setAutodisplay:YES];
	[bbcView setBBC:self];
	rt.origin.x = rt.origin.y = 0.0;
	rt.size.width = rt.size.height = 200.0;
	scrollView = [[ScrollView alloc] initFrame:&rt];
	[scrollView setDocView:bbcView];
	[scrollView setCopyOnScroll:YES];
	[[scrollView setVertScrollerRequired:YES] setHorizScrollerRequired:YES];
	dirFwd = YES;
	etDoc = nil;
	hres = 10;
	vres = 10;
	[bbcView setRes:hres :vres];
	return self;
}

- initFromPboard:thePboard inDoc:theDoc linked:(BOOL) linked
{
	[self init];
	etDoc = theDoc;
	[etDoc registerNotification:self];
	isLinked = linked;
	if (thePboard != nil) {
		// 2 cases: we have a filename or fcontents on the pboard
		if ([thePboard findAvailableTypeFrom:&NXFilenamePboardType
					num:1]) 
		{
			char	*path;
			int		len;
			FILE *infile;
			int i,j;
			char c;
			
			isLinked = linked;
			[thePboard readType:NXFilenamePboardType data:&path length:&len];
			if (index(path, '\t')) *index(path, '\t')=0;
			if (isLinked)
				theName = NXUniqueString(path);
			else theName = NXUniqueString(rindex(path, '/')+1);
			infile = fopen(path,"r");
			fscanf(infile,"Hres %d Vres %d Reps %d\n",&hres,&vres,&i);
			state = malloc(vres* sizeof(char*));
			oldstate = malloc(vres* sizeof(char*));
			i = -1;
			while(++i < vres){
				state[i] = malloc(hres*sizeof(char));
				oldstate[i] = malloc(hres*sizeof(char));
				j = -1;
				while(++j < hres){
					fscanf(infile,"%c", &c);
					switch (c) {
						case 'O':{	state[i][j] = c;
										break;}
						case 'N':{	state[i][j] = c;
										break;}
						case 'S':{	state[i][j] = c;
										break;}
						case 'W':{	state[i][j] = c;
										break;}
						case 'E':{	state[i][j] = c;
										break;}
						case ';':
						case '.':
						default :{	state[i][j] = '.';
										break;}
					}
				}
				fscanf(infile,"%c", &c);
			}
			fclose(infile);
			[thePboard deallocatePasteboardData:path length:len];
		} else if ([thePboard findAvailableTypeFrom:&NXFileContentsPboardType 
					num:1]) {
			NXLogError("Hey! BBC found fileContents WITHOUT a filename");
		} else 
			NXBeep();
	} else {
		int i,j;
		
		state = malloc(vres * sizeof(char*));
		oldstate = malloc(vres * sizeof(char*));
		i = -1;
		while(++i < vres){
			state[i] = malloc(hres*sizeof(char));
			oldstate[i] = malloc(hres*sizeof(char));
			for(j=0; j<hres; j++) state[i][j] = '.';
		}
	}
	[bbcView setRes:hres :vres];
	if ([bbcView window]) {
		NXRect bbcRt, winRt;
		
		[bbcView getBounds:&bbcRt];
		[[bbcView window] getBounds:&winRt];
		if (bbcRt.size.width > (winRt.size.width - 72.0))
			bbcRt.size.width = (winRt.size.width - 72.0);
		if (bbcRt.size.height > (winRt.size.height - 48.0))
			bbcRt.size.height = (winRt.size.height - 48.0);
		[scrollView sizeTo:bbcRt.size.height :bbcRt.size.width];
	}
	return self;
}

- free
{
	int i;
	
	[[NXApp inspector] invalidate];
	[etDoc unregisterNotification:self];
	[scrollView free];
	i = -1;
	while(++i < vres){
		free(state[i]);
		free(oldstate[i]);
	}
	free(state);
	free(oldstate);
	return [super free];
}

- calcCellSize:(NXSize *)theSize
{	
	NXRect theRect;
	
   	[scrollView getBounds:&theRect];
	theSize->width = theRect.size.width;
	theSize->height = theRect.size.height;
    return self;
}

- highlight:(const NXRect *)cellFrame inView:controlView lit:(BOOL)flag { return self;}

- drawSelf:(const NXRect *)cellFrame inView:controlView
{    
	if ([scrollView superview] == NULL) [controlView addSubview:scrollView];
	[scrollView setBackgroundGray:NX_WHITE];
	[scrollView moveTo:cellFrame->origin.x :cellFrame->origin.y];
	[scrollView display];
    return self;
}

- (BOOL) trackMouse:(NXEvent *)theEvent inRect:(const NXRect *)cellFrame
  ofView:controlView
{
	// does this ever get called at all? is the View higher in Responder ch?
	[[NXApp inspector] inspect:[[BBCUI new] setBBC:self]];
    return YES;
}

- (NXAtom) name {return theName;}
- (char *) filename {return theName;} // this doesn't work yet
- (BOOL) doesShowPath {return doesShowPath;}
- (BOOL) isAccelerated {return isAccelerated;}
- (BOOL) isEditable {return isEditable;}
- (int) hres {return hres;}
- (int) vres {return vres;}
- (char **) state {return state;}
- setName:(NXAtom) newName {theName = newName; return self;}
- setShowPath:(BOOL) newState {doesShowPath = newState; return self;}
- setAccelerated:(BOOL) newState {isAccelerated = newState; return self;}
- setEditable:(BOOL) newState {isEditable = newState; return self;}
- setRes:(int)h :(int)v
{
	char **newstate, **newoldstate;
	int i,j;
	
	newstate = malloc(v * sizeof(char*));
	newoldstate = malloc(v * sizeof(char*));
	i = -1;
	while(++i < v){
		newstate[i] = malloc(h*sizeof(char));
		newoldstate[i] = malloc(h*sizeof(char));
		for(j=0; j<h; j++){
		if ((i < vres) && (j < hres)) 
			newstate[i][j] = state[i][j];
		else newstate[i][j] = '.';
		}
	}

	i = -1;
	while(++i < vres){
		free(state[i]);
		free(oldstate[i]);
	}
	free(state);
	free(oldstate);
	state = newstate;
	oldstate = newoldstate;
	hres = h;
	vres = v;
	[bbcView setRes:h :v];
	if ([bbcView window]) {
		NXRect bbcRt, winRt;
		
		[bbcView getBounds:&bbcRt];
		[[bbcView window] getBounds:&winRt];
		if (bbcRt.size.width > (winRt.size.width - 72.0))
			bbcRt.size.width = (winRt.size.width - 72.0);
		if (bbcRt.size.height > (winRt.size.height - 48.0))
			bbcRt.size.height = (winRt.size.height - 48.0);
		[scrollView sizeTo:bbcRt.size.height :bbcRt.size.width];
	}
	return self;
}
- stepForward 
{
	if (!dirFwd) [self reverse];
	return [self step];
}
- stepBackward 
{
	if (dirFwd) [self reverse];
	return [self step];
}
- step
{
	int i,j, flag;		// flag exists to check for extraordinary 
						// conditions like collision on both corners
	
	if (state == NULL) return self;
	i = -1;
	while(++i < vres){
		j = -1;
		while(++j < hres){
			oldstate[i][j] = state[i][j];
		}
	}
/* Phase 1: compute new velocities */
	i = vres;
	while(i-- > 0){
		j = -1;
		while(++j < hres){
			switch (state[i][j]) {
				case 'N':{	flag = 0;
								switch (state(i-1,j-1)){
									case 'E':
									case 'S':
									case 'O': {	oldstate[i][j] = 'E';
													flag = 1;
													break;
												}
									default:	break;
								}
								switch (state(i-1,j+1)){
									case 'W':
									case 'S':
									case 'O': {	if (flag){
														oldstate[i][j] = 'S';
													} else {
														oldstate[i][j] = 'W';
													}
													break;
												}
									default:	break;
								}
								break;}
				case 'S':{	flag = 0;
								switch (state(i+1,j-1)){
									case 'N':
									case 'E':
									case 'O': {	oldstate[i][j] = 'E';
													flag = 1;
													break;
												}
									default:	break;
								}
								switch (state(i+1,j+1)){
									case 'N':
									case 'W':
									case 'O': {	if (flag){
														oldstate[i][j] = 'N';
													} else{
														oldstate[i][j] = 'W';
													}
													break;
												}
									default:	break;
								}
								break;}
				case 'W':{	flag = 0;
								switch (state(i-1,j-1)){
									case 'E':
									case 'S':
									case 'O': {	oldstate[i][j] = 'S';
													flag = 1;
													break;
												}
									default:	break;
								}
								switch (state(i+1,j-1)){
									case 'E':
									case 'N':
									case 'O': {	if (flag){
														oldstate[i][j] = 'E';
													} else{
														oldstate[i][j] = 'N';
													}
													break;
												}
									default:	break;
								}
								break;}
				case 'E':{	flag = 0;
								switch (state(i-1,j+1)){
									case 'W':
									case 'S':
									case 'O': {	oldstate[i][j] = 'S';
													flag = 1;
													break;
												}
									default:	break;
								}
								switch (state(i+1,j+1)){
									case 'W':
									case 'N':
									case 'O': {	if (flag){
														oldstate[i][j] = 'W';
													} else{
														oldstate[i][j] = 'N';
													}
													break;
												}
									default:	break;
								}
								break;}
				}
		}
	}
	
/* Phase 2: move balls */
	i = vres;
	while(i-- > 0){
		j = -1;
		while(++j < hres){
			switch (oldstate[i][j]) {
				case 'N':{	state[i][j] = ';';
								state(i-1,j) = 'N';
								break;}
				case 'S':{	state[i][j] = ';';
								state(i+1,j) = 'S';
								break;}
				case 'W':{	state[i][j] = ';';
								state(i,j-1) = 'W';
								break;}
				case 'E':{	state[i][j] = ';';
								state(i,j+1) = 'E';
								break;}
			}
		}
	}
	[bbcView display];
	return self;
}
- reverse
{
	int i,j;

	if (state == NULL) {return self;}
	i = vres;
	while(i-- > 0){
		j = -1;
		while(++j < hres){
			switch (state[i][j]) {
				case 'N':{	state[i][j] = 'S';
								break;}
				case 'S':{	state[i][j] = 'N';
								break;}
				case 'W':{	state[i][j] = 'E';
								break;}
				case 'E':{	state[i][j] = 'W';
								break;}
				}
		}
	}
	dirFwd = !dirFwd;
	return self;
}

- writeRichText:(NXStream *)stream forView:view
{
	if(!isLinked)
		NXPrintf(stream, "A %d %s", strlen(theName), theName);
	else NXPrintf(stream, "B %d %s", strlen(theName), theName);
	return self;
}
- readRichText:(NXStream *)stream forView:view 
{
	FILE *infile;
	int i,j;
	char c;
	char buf[MAXPATHLEN];
	
	if (!etDoc) etDoc = [view etDoc];
	[etDoc registerNotification:self];
	NXScanf(stream, "%c %d ", &isLinked, &i);
	isLinked -= 'A';
	if (i) NXRead(stream, buf, i);
	buf[i] = 0;
	theName = NXUniqueString(buf);
	if (isLinked && *buf && !access(buf,F_OK|R_OK)) {
		infile = fopen(buf,"r");
		fscanf(infile,"Hres %d Vres %d Reps %d\n",&hres,&vres,&i);
		state = malloc(vres* sizeof(char*));
		oldstate = malloc(vres* sizeof(char*));
		i = -1;
		while(++i < vres){
			j = -1;
			state[i] = malloc(hres*sizeof(char));
			oldstate[i] = malloc(hres*sizeof(char));
			while(++j < hres){
				fscanf(infile,"%c", &c);
				switch (c) {
					case 'O':{	state[i][j] = c;
									break;}
					case 'N':{	state[i][j] = c;
									break;}
					case 'S':{	state[i][j] = c;
									break;}
					case 'W':{	state[i][j] = c;
									break;}
					case 'E':{	state[i][j] = c;
									break;}
					case ';':
					case '.':
					default :{	state[i][j] = '.';
									break;}
				}
			}
			fscanf(infile,"%c", &c);
		}
		fclose(infile);
		[bbcView setRes:hres :vres];
		if ([bbcView window]) {
			NXRect bbcRt, winRt;
			
			[bbcView getBounds:&bbcRt];
			[[bbcView window] getBounds:&winRt];
			if (bbcRt.size.width > (winRt.size.width - 72.0))
				bbcRt.size.width = (winRt.size.width - 72.0);
			if (bbcRt.size.height > (winRt.size.height - 48.0))
				bbcRt.size.height = (winRt.size.height - 48.0);
			[scrollView sizeTo:bbcRt.size.height :bbcRt.size.width];
		}
	} else {
		sprintf(buf, "%s/%s.bbc", [[etDoc navinfo] path],theName);
		if (access(buf,F_OK|R_OK)) {
			state = malloc(vres * sizeof(char*));
			oldstate = malloc(vres * sizeof(char*));
			i = -1;
			while(++i < vres){
				state[i] = malloc(hres*sizeof(char));
				oldstate[i] = malloc(hres*sizeof(char));
				for(j=0; j<hres; j++) state[i][j] = '.';
			}
			[bbcView setRes:hres :vres];
			if ([bbcView window]) {
				NXRect bbcRt, winRt;
				
				[bbcView getBounds:&bbcRt];
				[[bbcView window] getBounds:&winRt];
				if (bbcRt.size.width > (winRt.size.width - 72.0))
					bbcRt.size.width = (winRt.size.width - 72.0);
				if (bbcRt.size.height > (winRt.size.height - 48.0))
					bbcRt.size.height = (winRt.size.height - 48.0);
				[scrollView sizeTo:bbcRt.size.height :bbcRt.size.width];
			}
		} else {
			infile = fopen(buf,"r");
			fscanf(infile,"Hres %d Vres %d Reps %d\n",&hres,&vres,&i);
			state = malloc(vres* sizeof(char*));
			oldstate = malloc(vres* sizeof(char*));
			i = -1;
			while(++i < vres){
				j = -1;
				state[i] = malloc(hres*sizeof(char));
				oldstate[i] = malloc(hres*sizeof(char));
				while(++j < hres){
					fscanf(infile,"%c", &c);
					switch (c) {
						case 'O':{	state[i][j] = c;
										break;}
						case 'N':{	state[i][j] = c;
										break;}
						case 'S':{	state[i][j] = c;
										break;}
						case 'W':{	state[i][j] = c;
										break;}
						case 'E':{	state[i][j] = c;
										break;}
						case ';':
						case '.':
						default :{	state[i][j] = '.';
										break;}
					}
				}
				fscanf(infile,"%c", &c);
			}
			fclose(infile);
			[bbcView setRes:hres :vres];
			if ([bbcView window]) {
				NXRect bbcRt, winRt;
				
				[bbcView getBounds:&bbcRt];
				[[bbcView window] getBounds:&winRt];
				if (bbcRt.size.width > (winRt.size.width - 72.0))
					bbcRt.size.width = (winRt.size.width - 72.0);
				if (bbcRt.size.height > (winRt.size.height - 48.0))
					bbcRt.size.height = (winRt.size.height - 48.0);
				[scrollView sizeTo:bbcRt.size.height :bbcRt.size.width];
			}
		}
	}
	return self;
}
- writeComponentToPath:(NXAtom)path inFormat:(int)theFormat
{
	char thePath[MAXPATHLEN];
	int	i,j;
	NXStream *stream;
	
	if (isLinked) return self;
	if (theFormat != ETFD_FORMAT) return self;
	sprintf(thePath,"%s/%s.bbc", path, theName);
	stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	NXPrintf(stream,"Hres %d Vres %d Reps %d\n",hres,vres, 1000);
	i = -1;
	while(++i < vres){
		j = -1;
		while(++j < hres){
			NXPutc(stream, (state[i][j] == ';' ? '.' : state[i][j]));
		}
		NXPutc(stream,'\n');
	}
	NXSaveToFile(stream, thePath);
	NXCloseMemory(stream, NX_FREEBUFFER);
	[etDoc registerComponent:NXUniqueString(rindex(thePath, '/')+1)];
	return self;
}
- writeASCII:(NXStream *)stream forView:view
{
	NXPrintf(stream, "The BBC Simulation \"%s\"\n", theName);
	return self;
}
- writeASCIIRef:(NXStream *)stream forView:view
{
	int	i,j;

	NXPrintf(stream, "The BBC simulation \"%s\", in Jan's CS20 format:\n", theName);
	NXPrintf(stream,"Hres %d Vres %d Reps %d\n",hres,vres, 1000);
	i = -1;
	while(++i < vres){
		j = -1;
		while(++j < hres){
			NXPutc(stream, (state[i][j] == ';' ? '.' : state[i][j]));
		}
		NXPutc(stream,'\n');
	}
	return self;
}


@end